{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Умные указатели (C++11)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "https://en.cppreference.com/w/cpp/memory/unique_ptr"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "https://en.cppreference.com/w/cpp/memory/shared_ptr"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "https://en.cppreference.com/w/cpp/memory/weak_ptr"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "https://en.cppreference.com/w/cpp/memory/unique_ptr/make_unique"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "https://en.cppreference.com/w/cpp/memory/shared_ptr/make_shared"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Доклады**:\n",
    "* [Back to Basics: Smart Pointers - Rainer Grimm - CppCon 2020](https://www.youtube.com/watch?v=sQCSX7vmmKY)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<br />"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "##### Мотивацонные примеры, зачем нужны умные указатели:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Вариант 1.** Мы пишем своих angry birds. Нужно уметь подгружать для игрушки ресурсы по сети."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```c++\n",
    "class NetworkSystem\n",
    "{\n",
    "public:\n",
    "    virtual ~NetworkSystem() = default;\n",
    "\n",
    "    virtual std::string load(const std::string& url) = 0;\n",
    "};\n",
    "\n",
    "class GameResourcesLoader\n",
    "{\n",
    "public:\n",
    "    GameResourcesLoader(NetworkSystem* network) {...}\n",
    "\n",
    "private:\n",
    "    NetworkSystem* network_;\n",
    "};\n",
    "\n",
    "class PromoEventsLoader\n",
    "{\n",
    "public:\n",
    "    PromoEventsLoader(NetworkSystem* network) {...}\n",
    "\n",
    "private:\n",
    "    NetworkSystem* network_;\n",
    "};\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "В чём проблема?"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<br />"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Вариант 2.** Мы реализуем первое домашнее задание и решили повыпендриваться с интерфейсами хешеров:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```c++\n",
    "class Hasher\n",
    "{\n",
    "public:\n",
    "    virtual ~Hasher() = default;\n",
    "\n",
    "    virtual std::uint32_t calc_hash(std::istream& is) = 0;\n",
    "};\n",
    "\n",
    "Hasher *create_hasher(HasherType type)\n",
    "{\n",
    "    if (type == CRC32)\n",
    "        return new HasherCRC32();\n",
    "    else\n",
    "        return new HasherSUM32();\n",
    "}\n",
    "\n",
    "bool run(const std::string& filename, HasherType type)\n",
    "{\n",
    "    std::ifstream ifs(filename);\n",
    "    if (!ifs)\n",
    "        return false;\n",
    "\n",
    "    Hasher *hasher = create_hasher(type);\n",
    "\n",
    "    std::cout << hasher->calc_hash(ifs);\n",
    "\n",
    "    delete hasher;\n",
    "}\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "В чём проблема?"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<br />"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Вариант 3.** Посложнее. В чём проблема здесь?"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```c++\n",
    "// Интерфейс работы с кодировкой видеоданных.\n",
    "class VideoCodec { ... }\n",
    "\n",
    "class VideoDecoder\n",
    "{\n",
    "public:\n",
    "    VideoDecoder()\n",
    "        : codecs_{\n",
    "            new VideoCodecDIVX(),\n",
    "            new VideoCodecX264(),\n",
    "            new VideoCodecXVID()\n",
    "        }\n",
    "    {}\n",
    "\n",
    "    ~VideoDecoder() {\n",
    "        for (auto* codec: codecs_)\n",
    "            delete codec;\n",
    "    }\n",
    "    \n",
    "    ...;\n",
    "\n",
    "private:\n",
    "    std::vector<VideoCodec*> codecs_;\n",
    "};\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<br />"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "##### Варианты умных (и не очень) указателей"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Эти правила появились с С++11, и отчасти являются следствием реализации умных указетелей, отчасти - договорённости. Те места, где правила нарушаются, должны быть в коде хорошо откомментированы.\n",
    "\n",
    "* `unique_ptr` - указатель, описывающий уникальное владение объектом\n",
    "* `shared_ptr` - указатель, описывающий множественное владние объектом\n",
    "* `weak_ptr` - указатель для отслеживания времени жизни shared-объектов\n",
    "* `raw pointer` - невладеющий указатель (`Item*`)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "__Замечание__: за исключением задокументированных случаев сырой указатель - невладеющий, код, работающий с сырым указателем, не должен вызывать `free` и `delete`. Т.е.:\n",
    "\n",
    "* метод возвращает невладеющий указатель, ответственность за удаление объекта лежит на классе\n",
    "\n",
    "```c++\n",
    "class ItemsStorage\n",
    "{\n",
    "    ...\n",
    "    Item* find(int key) const;\n",
    "};\n",
    "```\n",
    "\n",
    "* функция возвращает невладеющий указатель, она знает, что вызывающий код не должен управлять временем жизни объекта\n",
    "\n",
    "```c++\n",
    "const char* get_name()\n",
    "{\n",
    "    return \"Dobryna Nikitich\";\n",
    "}\n",
    "```\n",
    "\n",
    "* функция принимает невладеющий указатель, она не в праве управлять временем жизни объекта. Семантика указателя: объект может отсутствовать.\n",
    "\n",
    "```c++\n",
    "Home build_home(const Roof* roof);\n",
    "```\n",
    "\n",
    "* функция принимает невладеющий указатель и возвращает невладеющий указатель:\n",
    "\n",
    "```c++\n",
    "const char* find_char(const char* str, char c);\n",
    "```\n",
    "\n",
    "* особый случай - функции `malloc` && `free`:\n",
    "\n",
    "```c++\n",
    "void* malloc(size_t size);\n",
    "free(void *);\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<br />"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "##### std::unique_ptr IR"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Упрощённый вариант для демонстрации"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```c++\n",
    "template<typename T>\n",
    "class unique_ptr\n",
    "{\n",
    "public:\n",
    "    unique_ptr() : ptr(nullptr) {}\n",
    "    \n",
    "    unique_ptr(T* p) : ptr(p) {}\n",
    "    \n",
    "    ~unique_ptr()\n",
    "    {\n",
    "        delete ptr;\n",
    "    }\n",
    "    \n",
    "    // указатель уникальный - копировать его нельзя\n",
    "    unique_ptr(const unique_ptr&) = delete;\n",
    "    unique_ptr& operator = (const unique_ptr&) = delete;\n",
    "    \n",
    "    // а перемещать из одного объекта в другой - можно:\n",
    "    unique_ptr(unique_ptr&& rhs) noexcept\n",
    "    {\n",
    "        ptr = rhs.ptr;\n",
    "        rhs.ptr = nullptr;  // 1. ~unique_ptr отработает нормально?\n",
    "                            // 2. nullptr - специфицирован!\n",
    "    }\n",
    "    unique_ptr& operator = (unique_ptr&& rhs) noexcept\n",
    "    {\n",
    "        if (this != &rhs)\n",
    "        {\n",
    "            delete ptr;\n",
    "\n",
    "            ptr = rhs.ptr;\n",
    "            rhs.ptr = nullptr;  \n",
    "        }\n",
    "        return *this;\n",
    "    }\n",
    "    \n",
    "    T& operator ->() const noexcept\n",
    "    {\n",
    "        return *ptr;\n",
    "    }    \n",
    "\n",
    "private:\n",
    "    T* ptr;\n",
    "};\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Чему равен `sizeof` такого `unique_ptr` ?"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<br />"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### std::make_unique"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Пример:\n",
    "\n",
    "```c++\n",
    "std::unique_ptr<Person> create_person(std::unique_ptr<Head> head,\n",
    "                                      std::unique_ptr<Body> body);\n",
    "\n",
    "create_person(new Head, new Body);\n",
    "```\n",
    "\n",
    "Напомните, в чём проблема?"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<br />"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Теперь мы грамотные и можем исправить проблему:\n",
    "\n",
    "```c++\n",
    "create_person(std::unique_ptr<Head>(new Head),\n",
    "              std::unique_ptr<Body>(new Body));\n",
    "```\n",
    "\n",
    "Почему проблема до сих пор с нами?"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<br />"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Корректное решение:\n",
    "\n",
    "```c++\n",
    "create_person(std::make_unique<Head>(),\n",
    "              std::make_unique<Body>());\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Замечание**: Проблема с утечкой в коде\n",
    "\n",
    "```c++\n",
    "create_person(std::unique_ptr<Head>(new Head),\n",
    "              std::unique_ptr<Body>(new Body));\n",
    "```\n",
    "\n",
    "Актуальна только до С++14. В стандарте С++17 скорректировали правила порядка вычислений аргументов ([пункт 15](https://en.cppreference.com/w/cpp/language/eval_order)), и теперь аргумент функции обязан быть вычислен полностью до вычисления другого аргумента, но порядок вычисления аргументов всё равно не определён.\n",
    "\n",
    "```c++\n",
    "// undefined   behavior until C++17\n",
    "// unspecified behavior since C++17\n",
    "int i = 0;\n",
    "f(++i, ++i);\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<br />"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`std::make_unique` умеет передавать аргументы в конструктор:\n",
    "\n",
    "```c++\n",
    "class Head {\n",
    "public:\n",
    "    Head(Color eyes_color, float nose_length_cm);\n",
    "    ...;\n",
    "};\n",
    "\n",
    "std::unique_ptr<Head> buratino_head = std::make_unique<Head>(Color::Blue, 25);\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<br />"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "##### Как пользоваться std::unique_ptr"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Пример 1** для angry birds\n",
    "\n",
    "```c++\n",
    "// Интерфейс работы с сетью в приложении.\n",
    "class INetwork { ... }\n",
    "\n",
    "// Интерфейс доступа до ресурсов игры.\n",
    "class IResources { ... }\n",
    "\n",
    "// Контекст игровой сессии.\n",
    "class SessionContext {\n",
    "public:\n",
    "    SessionContext(std::unique_ptr<INetwork> network,\n",
    "                   std::unique_ptr<IResources> resources)\n",
    "        : network_(std::move(network))\n",
    "        , resources_(std::move(resources))\n",
    "    {}\n",
    "    \n",
    "    // доступ до актуальной реализации сети\n",
    "    INetwork& network() const { return *network_; }\n",
    "    \n",
    "    // доступ до актуального хранилища ресурсов\n",
    "    IResources& resources() const { return *resources_; }\n",
    "    \n",
    "private:\n",
    "    std::unique_ptr<INetwork> network_;\n",
    "    std::unique_ptr<IResources> resources_;\n",
    "};\n",
    "\n",
    "// Создаём контекст для игрока.\n",
    "SessionContext makeSessionContextForGame() {\n",
    "    return SessionContext(\n",
    "        std::make_unique<NetworkImpl>(),\n",
    "        std::make_unique<ResourcesImpl>());\n",
    "}\n",
    "\n",
    "// Создаём контекст для тестирования.\n",
    "SessionContext makeSessionContextForTesting() {\n",
    "    return SessionContext(\n",
    "        std::make_unique<NetworkMock>(),\n",
    "        std::make_unique<ResourcesMock>());\n",
    "}\n",
    "\n",
    "// Создаём контекст для презентации заказчику.\n",
    "SessionContext makeSessionContextForPresentation() {\n",
    "    return SessionContext(\n",
    "        std::make_unique<SuperFastNetwork>(),\n",
    "        std::make_unique<CachedResources>());\n",
    "}\n",
    "\n",
    "\n",
    "void use(SessionContext& context) {\n",
    "    INetwork& network = context.network();    \n",
    "    network.load();\n",
    "}\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Пример 2** для hasher\n",
    "\n",
    "```c++\n",
    "class Hasher\n",
    "{\n",
    "public:\n",
    "    virtual ~Hasher() = default;\n",
    "    virtual std::uint32_t calc_hash(std::istream& is) = 0;\n",
    "};\n",
    "\n",
    "std::unique_ptr<Hasher> create_hasher(HasherType type)\n",
    "{\n",
    "    if (type == CRC32)\n",
    "        return std::make_unique<HasherCRC32>();\n",
    "    else\n",
    "        return std::make_unique<HasherSUM32>();\n",
    "}\n",
    "\n",
    "bool run(const std::string& filename, HasherType type)\n",
    "{\n",
    "    std::ifstream ifs(filename);\n",
    "    if (!ifs)\n",
    "        return false;\n",
    "\n",
    "    std::unique_ptr<Hasher> hasher = create_hasher(type);\n",
    "    std::cout << hasher->calc_hash(ifs);\n",
    "}\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Пример 3:** для набора кодеков\n",
    "\n",
    "```c++\n",
    "// Интерфейс работы с кодировкой видеоданных.\n",
    "class VideoCodec { ... }\n",
    "\n",
    "class VideoDecoder\n",
    "{\n",
    "public:\n",
    "    VideoDecoder()\n",
    "    {\n",
    "        codecs_.reserve(3);\n",
    "        codecs_.emplace_back(std::make_unique<VideoCodecDIVX>());\n",
    "        codecs_.emplace_back(std::make_unique<VideoCodecX264>());\n",
    "        codecs_.emplace_back(std::make_unique<VideoCodecXVID>());    \n",
    "    }\n",
    "\n",
    "    ...;\n",
    "\n",
    "private:\n",
    "    std::vector<std::unique_ptr<VideoCodec>> codecs_;\n",
    "};\n",
    "\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<br />"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "##### std::shared_ptr IR"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Перед тем как показать ответ, попробуйте ответить на следующие вопросы:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Объект должен умереть, тогда и только тогда когда умрёт __последний__ его владелец. Какую информацию придётся хранить?"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Где эту информацию хранить? Как насчёт такого примера:\n",
    "\n",
    "```c++\n",
    "std::shared_ptr<T> a(new T);\n",
    "std::shared_ptr<T> b = a;\n",
    "std::shared_ptr<T> c = b;\n",
    "\n",
    "a.reset();\n",
    "b.reset();\n",
    "\n",
    "// объект должен жить, т.к. им владеют через c\n",
    "\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<br />"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ответ:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "![](sp_internals.PNG)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<br />"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "##### Как работает shared_ptr"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Потренируемся на кошках. Эти примеры надо рисовать на доске (что происходит в памяти), так понимают лучше"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```c++\n",
    "{\n",
    "    std::shared_ptr<Cat> cat1(new Cat);\n",
    "\n",
    "    std::shared_ptr<Cat> cat2;\n",
    "\n",
    "    cat2 = cat1;\n",
    "\n",
    "    std::shared_ptr<Cat> cat3 = cat1;\n",
    "\n",
    "    cat1.reset();\n",
    "\n",
    "    std::weak_ptr<Cat> weak_cat = cat2;\n",
    "\n",
    "    std::shared_ptr<Cat> cat4 = weak_cat.lock();\n",
    "    if (cat4)\n",
    "        ...;\n",
    "}\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<br />"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "##### Специфические ошибки с shared_ptr"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "* Один объект - несколько кластеров `shared_ptr`-ов\n",
    "\n",
    "    ```c++\n",
    "    {\n",
    "        Cat* y = new Cat();\n",
    "        std::shared_ptr<Cat> cat1(y);\n",
    "        std::shared_ptr<Cat> cat2(y);\n",
    "    }\n",
    "    ```\n",
    "\n",
    "* циклы владения\n",
    "\n",
    "```c++\n",
    "class Cat {\n",
    "public:\n",
    "    void set_frient(std::shared_ptr<Cat> f) {\n",
    "        friend_ = std::move(f);\n",
    "    }\n",
    "\n",
    "private:\n",
    "    std::shared_ptr<Cat> friend_;\n",
    "};\n",
    "\n",
    "{\n",
    "    auto barsic = std::shared_ptr<Cat>(new Cat);\n",
    "    auto marsic = std::shared_ptr<Cat>(new Cat);\n",
    "    barsic->set_friend(marsic);\n",
    "    marsic->set_friend(barsic);\n",
    "}\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<br />"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "##### make_shared - способ быстрее создавать shared_ptr"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "https://arne-mertz.de/2018/09/make_shared-vs-the-normal-shared_ptr-constructor/"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```c++\n",
    "std::vector<std::shared_ptr<Cat>> herd;\n",
    "herd.reserve(100);\n",
    "for (int i = 0; i < 100; ++i)\n",
    "    herd.push_back(std::shared_ptr<Cat>(new Cat()));\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "![](shared_ptr_naive.png)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "200 аллокаций на объекты + 1 аллокация на вектор"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<br />"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```c++\n",
    "std::vector<std::shared_ptr<Cat>> herd;\n",
    "herd.reserve(100);\n",
    "for (int i = 0; i < 100; ++i)\n",
    "    herd.push_back(std::make_shared<Cat>());\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "![](shared_ptr_make_shared.png)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "100 аллокаций на объекты + 1 аллокация на вектор"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<br />"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "##### custom deleter && scope exit"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Умным указателям можно задавать пользовательские deleter-ы.\n",
    "\n",
    "Может быть полезно для:\n",
    "1. управления не-RAII - объектами\n",
    "2. донастройки разрушения объекта\n",
    "3. отлавливания момента, когда объект разрушен"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Пример 1: управление не-RAII-объектами.\n",
    "        \n",
    "```c++\n",
    "void write_to_csv(const char* filename)\n",
    "{\n",
    "    FILE* fo = fopen(filename, \"w\");\n",
    "    // код записи данных в файл тут\n",
    "    // TODO: в чём потенциальная проблема такой реализации?\n",
    "    fclose(fo);\n",
    "}\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```c++\n",
    "void write_to_csv(const char* filename)\n",
    "{\n",
    "    std::unique_ptr<FILE, int (*)(FILE*)> fo(fopen(filename, \"w\"),\n",
    "                                             &fclose);\n",
    "    // код записи данных в файл тут\n",
    "}\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Как это читать смертным:\n",
    "\n",
    "```c++\n",
    "// уникальное владение ресурсом\n",
    "std::unique_ptr<\n",
    "                // ресурс - FILE, внтури хранится FILE*\n",
    "                FILE,\n",
    "                // тип нестандартного удалятора (в данном случае - сигнатура функции, т.к. передаётся функция)\n",
    "                int (*)(FILE*)>\n",
    "                // имя переменной + вызов конструктора\n",
    "                fo(\n",
    "                    // ресурс, которым владеем\n",
    "                    fopen(filename, \"r\"),\n",
    "                    // нестандартный удалятор - указатель на функцию\n",
    "                    &fclose);\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Вариации:\n",
    "\n",
    "```c++\n",
    "// изначальный вариант\n",
    "std::unique_ptr<FILE, int (*)(FILE*)> p(fopen(\"input.txt\", \"r\"),\n",
    "                                        &fclose);\n",
    "\n",
    "// если лень вручную вспоминать тип удалятора\n",
    "std::unique_ptr<FILE, decltype(&fclose)> p(fopen(\"input.txt\", \"r\"),\n",
    "                                           &fclose);\n",
    "\n",
    "// через deleter functor\n",
    "struct FileCloser {  \n",
    "    void operator()(FILE* f) {\n",
    "        fclose(f);\n",
    "    }\n",
    "};\n",
    "\n",
    "std::unique_ptr<FILE, FileCloser> p(fopen(\"input.txt\", \"r\"),\n",
    "                                    FileCloser());\n",
    "\n",
    "// через лямбду\n",
    "auto close_file = [](FILE* f) { fclose(f); };\n",
    "std::unique_ptr<FILE, decltype(close_file)> p(fopen(\"input.txt\", \"r\"),\n",
    "                                              close_file);\n",
    "\n",
    "// shared_ptr дороже в runtime, но приятнее конструировать,\n",
    "//            не нужно прямо указывать тип deleter\n",
    "std::shared_ptr<FILE> p(fopen(\"input.txt\", \"r\"), close_file);\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Пример 2: донастройка разрушения объекта\n",
    "\n",
    "```c++\n",
    "std::shared_ptr<FILE> p(fopen(\"error.log\", \"r\"),\n",
    "                        [](FILE* f) {\n",
    "                            std::cout << \"you are going to die!\\n\";\n",
    "                            fclose(f);\n",
    "                            \n",
    "                            schedule_send_error_log_to_sever(\"error.log\");\n",
    "                        });\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "__Вопрос:__ чего не должны делать custom deleter?\n",
    "\n",
    "<details>\n",
    "<summary>Подсказка</summary>\n",
    "<p>\n",
    "\n",
    "Бросать исключений!\n",
    "\n",
    "</p>\n",
    "</details>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<br />"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Правила работы с уникальными указателями:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "* Владение объектом:\n",
    "  * по значению\n",
    "  * через `std::unique_ptr` в случае единоличного владения\n",
    "  * через `std::shared_ptr` в случае множественного владения\n",
    "  \n",
    "  Не надо так!\n",
    "  ```c++\n",
    "  struct Point\n",
    "  {\n",
    "      std::unique_ptr<float> x;\n",
    "      std::unique_ptr<float> y;\n",
    "  };\n",
    "  ```\n",
    "  \n",
    "  Будьте человечнее:\n",
    "  ```c++\n",
    "  struct Point\n",
    "  {\n",
    "      float x;\n",
    "      float y;\n",
    "  };\n",
    "  ```\n",
    "  \n",
    "* В нормальном коде почти никогда не нужно явно вызывать `new`/`delete`. Для этого есть `std::make_unique` и `std::make_shared`:\n",
    "  * `make_unique` помогает избежать проблем с утечкой при исключениях\n",
    "  * `make_shared` дополнительно работает быстрее конструктора `shared_ptr`, т.к. выполняет меньше аллокаций\n",
    "\n",
    "* Для разрыва циклов владения используйте `std::weak_ptr`\n",
    "\n",
    "* Не надо без нужды передавать в функцию умный указатель - передавайте сам объект. Умный указатель передаётся только в том случае, если функция работает с владением объекта:\n",
    "\n",
    "    Случай, когда функции не должны работать со способом владения объектом:\n",
    "    ```c++\n",
    "    void print_person(const Person& person) {\n",
    "        std::cout << person.name << ' ' << person.surname << ' ' << person.age;\n",
    "    }\n",
    "\n",
    "    void make_elder(Person& person) {\n",
    "        ++person.age;\n",
    "\n",
    "        if (person.age == 33)\n",
    "            std::cout << \"time to fight!\" << std::endl;\n",
    "    }\n",
    "\n",
    "    std::shared_ptr<Person> ilya = std::make_shared<Person>(\"Ilya\", \"Muromec\", 32);\n",
    "    print_person(*ilya);\n",
    "    make_elder(*ilya);\n",
    "    ```\n",
    "    \n",
    "    Случай, когда должны:\n",
    "    ```c++\n",
    "    class Squad {\n",
    "    public:\n",
    "        void add_warrior(std::shared_ptr<Person> warrior) {\n",
    "            warriors_.push_back(warrior);\n",
    "        }\n",
    "        \n",
    "    private:\n",
    "        std::vector<std::shared_ptr<Person>> warriors_;\n",
    "    };\n",
    "    \n",
    "    Squad s;\n",
    "    std::shared_ptr<Person> ilya = std::make_shared<Person>(\"Ilya\", \"Muromec\", 32);\n",
    "    s.add_warrior(ilya);\n",
    "    ```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<br />"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "За пределами темы:\n",
    "\n",
    "* custom allocator\n",
    "* `shared_from_this`\n",
    "* специальные `static_pointer_cast` / `dynamic_pointer_cast` для умных указателей\n",
    "* `std::shared_ptr` to class members\n",
    "* intrusive pointers"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.9"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
